package org.batatasframework.bytecode;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.batatasframework.bytecode.spi.AsmInvokable;
import org.batatasframework.bytecode.spi.AsmLoadable;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.util.Assert;

public class MethodAsminfo implements AsmInvokable{
	
	private final ClassAsminfo owner;
	private final String name;
	/** 如果是创建的方法，则没有这个值 */
	private Method method;
	
	private ClassAsminfo resultClass;
	private ClassAsminfo[] paramClass;
	
	/** 解析方法的参数 */
	public static ClassAsminfo[] paramTypes(Method method){
		return ClassAsminfo.toArray(method.getParameterTypes());
	}
	
	/** 解析方法的返回值 */
	public static ClassAsminfo returnType(Method method){
		return new ClassAsminfo(method.getReturnType());
	}
	
	public static Method find(Class<?> cls, String name, Class<?>... params) throws Exception{
		return cls.getMethod(name, params);
	}
	
	public MethodAsminfo(ClassAsminfo owner, ClassAsminfo resultClass, String name, ClassAsminfo... params){
		owner.add(this);
		
		this.owner = owner;
		this.name = name;
		this.resultClass = resultClass;
		this.paramClass = params;
	}
	
	public MethodAsminfo(ClassAsminfo owner, String name, ClassAsminfo... params){
		this(owner, ClassAsminfo.T_VOID, name, params);
	}

	public MethodAsminfo(ClassAsminfo owner, String name, Class<?>... params) throws Exception{
		this(owner, find(owner.getType(), name, params));
	}

	public MethodAsminfo(ClassAsminfo owner, Method method){
		this(owner, returnType(method), method.getName(), paramTypes(method));
		this.method = method;
	}
	
	public ClassAsminfo[] getParamClass() {
		return paramClass;
	}
	
	public ClassAsminfo getResultClass() {
		return resultClass;
	}
	
	public boolean check(String name, Class<?>...pts){
		if(this.name.equals(name) && pts.length == paramClass.length){
			for(int i = 0; i < pts.length; i++){
				if(!paramClass[i].ckeck(pts[i])) return false;
			}
			return true;
		}
		return false;
	}
	
	public void visit(MethodVisitor visitor){
		if(null != method && Modifier.isStatic(method.getModifiers())){
			visitor.visitMethodInsn(Opcodes.INVOKESTATIC, owner.getClassPath(), name, getDes(), false);
		}else if(owner.isInterface()){
			visitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner.getClassPath(), name, getDes(), true);
		}else{
			visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner.getClassPath(), name, getDes(), false);
		}
	}

	@Override
	public void invoke(AsmLoadable object, MethodVisitor visitor, AsmLoadable... getters) {
		_assertParam(getters);
		
		object.load(visitor);
		_invoke(visitor, getters);
	}

	@Override
	public void invoke(MethodVisitor visitor, AsmLoadable... getters) {
		_assertParam(getters);
		
		_invoke(visitor, getters);
	}

	private void _assertParam(AsmLoadable... getters) {
		Assert.isTrue(paramClass.length != getters.length, "参数不一致：" + toString());
	}
	
	private void _invoke(MethodVisitor visitor, AsmLoadable... getters) {
		for(int i = 0; i < paramClass.length; i++){
			getters[i].load(visitor, paramClass[i]);
		}
		visit(visitor);
	}
	
	public String getDes(){
		String des = "(";
		for(ClassAsminfo param : paramClass){
			des += param.toDes();
		}
		des += ")";
		if(null == resultClass){
			des += "V";
		}else{
			des += resultClass.toDes();
		}
		return des;
	}
	
	@Override
	public String toString() {
		String str = owner.getClassName() + "#" + name + "(";
		for(ClassAsminfo param : paramClass){
			str += param.toDes();
		}
		str += ")" + resultClass.toDes();
		return str;
	}
	
	public ClassAsminfo getOwner() {
		return owner;
	}
}
